﻿/// <copyright file="ServiceMangerInternal.cs" company="not for commercial use">
/// Copyright (c) 6/4/2012 All Right Reserved
/// </copyright>
/// <author>Dimitry Kaplin</author>
/// <date>6/4/2012 19:33 </date>
/// <summary>methods for managing service</summary>
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Windows.Forms;

namespace EthernetTools.Utils
{
    class ServiceMangerInternal
    {
        #region Properties

        /// <summary>
        /// NdisProt string service name
        /// </summary>
        static public string NdisProtServiceName = "ndisprot";

        /// <summary>
        /// Result of last GetServiceStatus() successfull function
        /// </summary>
        static public SERVICE_STATUS LastServiceStatus
        {
            get;
            private set;
        }

        #endregion

        #region Status enums

        public enum ServiceType
        {
            /// <summary>
            /// The service is a file system driver.
            /// </summary>
            SERVICE_KERNEL_DRIVER       = 0x00000001,

            /// <summary>
            /// The service is a device driver.
            /// </summary>
            SERVICE_FILE_SYSTEM_DRIVER  = 0x00000002,

            /// <summary>
            /// The service runs in its own process.
            /// </summary>
            SERVICE_WIN32_OWN_PROCESS   = 0x00000010,

            /// <summary>
            /// The service shares a process with other services.
            /// </summary>
            SERVICE_WIN32_SHARE_PROCESS = 0x00000020,

            /// <summary>
            /// The service can interact with the desktop. 
            /// </summary>
            SERVICE_INTERACTIVE_PROCESS = 0x00000100
        }

        public enum ServiceCurrentState
        {
            /// <summary>
            /// The service continue is pending.
            /// </summary>
            SERVICE_CONTINUE_PENDING    = 0x00000005,

            /// <summary>
            /// The service pause is pending.
            /// </summary>
            SERVICE_PAUSE_PENDING       = 0x00000006,

            /// <summary>
            /// The service is paused.
            /// </summary>
            SERVICE_PAUSED              = 0x00000007, 
            
            /// <summary>
            /// The service is running.
            /// </summary>
            SERVICE_RUNNING             = 0x00000004,
            
            /// <summary>
            /// The service is starting.
            /// </summary>
            SERVICE_START_PENDING       = 0x00000002,

            /// <summary>
            /// The service is stopping.
            /// </summary>
            SERVICE_STOP_PENDING        = 0x00000003,
            
            /// <summary>
            /// The service is not running.
            /// </summary>
            SERVICE_STOPPED             = 0x00000001
        }
        
        public enum ServiceLastError : ulong
        {
            /// <summary>
            /// The handle does not have the required access right.
            /// </summary>
            ERROR_ACCESS_DENIED              = 5L,

            /// <summary>
            /// The service cannot be stopped because other running services are dependent on it.
            /// </summary>
            ERROR_DEPENDENT_SERVICES_RUNNING = 1051L,

            /// <summary>
            /// The specified handle was not obtained using CreateService or OpenService, or the handle is no longer valid.
            /// </summary>
            ERROR_INVALID_HANDLE             = 6L,

            /// <summary>
            /// The requested control code is undefined.
            /// </summary>
            ERROR_INVALID_PARAMETER          = 87L,
            
            /// <summary>
            /// The requested control code is not valid, or it is unacceptable to the service.
            /// </summary>
            ERROR_INVALID_SERVICE_CONTROL    = 1052L,

            /// <summary>
            /// The requested control code cannot be sent to the service because the state of the service is SERVICE_STOPPED, SERVICE_START_PENDING, or SERVICE_STOP_PENDING.
            /// </summary>
            ERROR_SERVICE_CANNOT_ACCEPT_CTRL = 1061L,

            /// <summary>
            /// The service has not been started.
            /// </summary>
            ERROR_SERVICE_NOT_ACTIVE         = 1062L,
            
            /// <summary>
            /// The process for the service was started, but it did not call StartServiceCtrlDispatcher, or the thread that called StartServiceCtrlDispatcher may be blocked in a control handler function.
            /// </summary>
            ERROR_SERVICE_REQUEST_TIMEOUT    = 1053L,

            /// <summary>
            /// The system is shutting down.
            /// </summary>
            ERROR_SHUTDOWN_IN_PROGRESS       = 1115L,

            /// <summary>
            /// The database specified does not exist.
            /// </summary>
            ERROR_DATABASE_DOES_NOT_EXIST    = 1065L,

            /// <summary>
            /// The specified service name is invalid.
            /// </summary>
            ERROR_INVALID_NAME               = 123L,

            /// <summary>
            /// The specified service does not exist.
            /// </summary>
            ERROR_SERVICE_DOES_NOT_EXIST     = 1060L,

            /// <summary>
            /// The service binary file could not be found.
            /// </summary>
            ERROR_PATH_NOT_FOUND             = 3L,

            /// <summary>
            /// An instance of the service is already running.
            /// </summary>
            ERROR_SERVICE_ALREADY_RUNNING    = 1056L,

	        /// <summary>
            /// The database is locked.
            /// </summary>
            ERROR_SERVICE_DATABASE_LOCKED    = 1055L,

	        /// <summary>
	        /// The service depends on a service that does not exist or has been marked for deletion.
	        /// </summary>
            ERROR_SERVICE_DEPENDENCY_DELETED = 1075L,

	
            /// <summary>
            /// The service depends on another service that has failed to start.
            /// </summary>
            ERROR_SERVICE_DEPENDENCY_FAIL     = 1068L,

            /// <summary>
            /// The service has been disabled.
            /// </summary>
            ERROR_SERVICE_DISABLED            = 1058L,

            /// <summary>
            /// The service did not start due to a logon failure. This error occurs if the service is configured to run under an account that does not have the "Log on as a service" right.
            /// </summary>
            ERROR_SERVICE_LOGON_FAILED        = 1069L,
            
            /// <summary>
            /// The service has been marked for deletion.
            /// </summary>
            ERROR_SERVICE_MARKED_FOR_DELETE   = 1072L,

            /// <summary>
            /// A thread could not be created for the service.
            /// </summary>
            ERROR_SERVICE_NO_THREAD           = 1054L,

            /// <summary>
            /// A circular service dependency was specified.
            /// </summary>
            ERROR_CIRCULAR_DEPENDENCY         = 1059L,

            /// <summary>
            /// The display name already exists in the service control manager database either as a service name or as another display name.
            /// </summary>
            ERROR_DUPLICATE_SERVICE_NAME      = 1078L,

            /// <summary>
            /// The user account name specified in the lpServiceStartName parameter does not exist.
            /// </summary>
            ERROR_INVALID_SERVICE_ACCOUNT      = 1057L,

            /// <summary>
            /// The specified service already exists in this database.
            /// </summary>
            ERROR_SERVICE_EXISTS               = 1073L,

            /// <summary>
            /// Windows cannot verify the digital signature for this file. A recent hardware or software change might have installed a file that is signed incorrectly or damaged, or that might be malicious software from an unknown source.
            /// </summary>
            ERROR_INVALID_IMAGE_HASH           = 0x241,

        }

        #endregion

        #region Imports

        [DllImport("SharpWinApiWrapper.dll")]
        private static extern unsafe bool StartServiceWrapper(
            [MarshalAs(UnmanagedType.LPWStr)]String ServiceName, 
            out int LastError);

        [DllImport("SharpWinApiWrapper.dll")]
        private static extern unsafe bool StopServiceWrapper(
            [MarshalAs(UnmanagedType.LPWStr)]String ServiceName,
            out int LastError);

        [DllImport("SharpWinApiWrapper.dll")]
        private static extern unsafe bool ContinueServiceWrapper(
            [MarshalAs(UnmanagedType.LPWStr)]String ServiceName,
            out int LastError);

        [DllImport("SharpWinApiWrapper.dll")]
        private static extern unsafe bool PauseServiceWrapper(
            [MarshalAs(UnmanagedType.LPWStr)]String ServiceName,
            out int LastError);

        [DllImport("SharpWinApiWrapper.dll")]
        private static extern unsafe bool InstallServiceWrapper(
            [MarshalAs(UnmanagedType.LPWStr)]String ServiceName,
            out int LastError,
            [MarshalAs(UnmanagedType.LPWStr)]String ExecutablePath);

        [DllImport("SharpWinApiWrapper.dll")]
        private static extern unsafe bool RemoveServiceWrapper(
            [MarshalAs(UnmanagedType.LPWStr)]String ServiceName,
            out int LastError);
        
        [DllImport("SharpWinApiWrapper.dll")]
        private static extern unsafe bool GetServiceStatusWrapper(
            [MarshalAs(UnmanagedType.LPWStr)]String ServiceName, 
            out int LastError,
            out SERVICE_STATUS ServiceStatus);
        
        [StructLayout(LayoutKind.Sequential)]
		public struct SERVICE_STATUS
        {
            /// <summary>
            /// The type of service
            /// </summary>
			public int dwServiceType;

            /// <summary>
            /// The current state of the service.
            /// </summary>
            public int dwCurrentState;   

            /// <summary>
            /// The control codes the service accepts and processes in its handler function 
            /// (see Handler and HandlerEx). 
            /// A user interface process can control a service by specifying a control command 
            /// in the ControlService or ControlServiceEx function. 
            /// By default, all services accept the SERVICE_CONTROL_INTERROGATE value. 
            /// To accept the SERVICE_CONTROL_DEVICEEVENT value, the service must register to receive 
            /// device events by using the RegisterDeviceNotification function. 
            /// </summary>
            public int dwControlsAccepted;    

            /// <summary>
            /// The error code the service uses to report an error that occurs when it is starting 
            /// or stopping. To return an error code specific to the service, 
            /// the service must set this value to ERROR_SERVICE_SPECIFIC_ERROR to indicate 
            /// that the dwServiceSpecificExitCode member contains the error code. 
            /// The service should set this value to NO_ERROR when it is running 
            /// and on normal termination.
            /// </summary>
            public int dwWin32ExitCode; 
            
            /// <summary>
            /// A service-specific error code that the service returns 
            /// when an error occurs while the service is starting or stopping. 
            /// This value is ignored unless the dwWin32ExitCode member is set to 
            /// ERROR_SERVICE_SPECIFIC_ERROR.
            /// </summary>
            public int dwServiceSpecificExitCode;    

            /// <summary>
            /// The check-point value the service increments periodically to 
            /// report its progress during a lengthy start, stop, pause, or continue operation. 
            /// For example, the service should increment this value as it completes each step 
            /// of its initialization when it is starting up. 
            /// The user interface program that invoked the operation on the service uses this value 
            /// to track the progress of the service during a lengthy operation. 
            /// This value is not valid and should be zero when the service does not have a start, 
            /// stop, pause, or continue operation pending.
            /// </summary>
            public int dwCheckPoint;

            /// <summary>
            /// The estimated time required for a pending start, stop, pause, or continue operation, 
            /// in milliseconds. 
            /// Before the specified amount of time has elapsed, the service should make 
            /// its next call to the SetServiceStatus function with either an incremented dwCheckPoint 
            /// value or a change in dwCurrentState. 
            /// If the amount of time specified by dwWaitHint passes, and dwCheckPoint 
            /// has not been incremented or dwCurrentState has not changed, 
            /// the service control manager or service control program can assume 
            /// that an error has occurred and the service should be stopped. 
            /// However, if the service shares a process with other services, 
            /// the service control manager cannot terminate the service application because 
            /// it would have to terminate the other services sharing the process as well.
            /// </summary>
            public int dwWaitHint;    
		};

        #endregion

        #region Public Methods

        /// <summary>
        /// Get Specified service status
        /// </summary>
        /// <param name="ServiceName">Name of service</param>
        /// <returns>Struct with service status</returns>
        /// <exception cref="System.ComponentModel.Win32Exception"> When something wrong. Containing win32 error code</exception>
        public static SERVICE_STATUS GetServiceStatus(string ServiceName)
        {
            SERVICE_STATUS SS = new SERVICE_STATUS();
            int lastError = 0;

            bool successed = GetServiceStatusWrapper(ServiceName, out lastError, out SS);

            if (lastError != 0 && !successed)
            {
                string ERR_STR = "";
                try
                {
                    // cast ulong to LastError can throw an exception
                    ERR_STR = Enum.GetName(typeof(ServiceLastError), lastError);
                }
                catch (System.Exception){}

                throw new Win32Exception((int)lastError, "There was error, while trying to get service \"" + ServiceName + "\" status;" + ERR_STR);
            }

            LastServiceStatus = SS;
            return SS;
        }
       
        /// <summary>
        /// Starts specified service
        /// </summary>
        /// <param name="ServiceName">Name of service</param>
        /// <returns>True if start was successed, or if service was already running</returns>
        /// <exception cref="System.ComponentModel.Win32Exception"> When something wrong. Containing win32 error code</exception>
        public static bool StartService(string ServiceName)
        {
            int lastError = 0;

            bool successed = StartServiceWrapper(ServiceName, out lastError);

            if (lastError != 0 && !successed)
            {
                string ERR_STR = "";
                try
                {
                    // cast ulong to LastError can throw an exception
                    ERR_STR = Enum.GetName(typeof(ServiceLastError), lastError);
                }
                catch (System.Exception) { }

                if (lastError == (int)ServiceMangerInternal.ServiceLastError.ERROR_INVALID_IMAGE_HASH)
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine("");
                    sb.AppendLine("Windows cannot verify the digital signature for this file. A recent hardware or software change might have installed a file that is signed incorrectly or damaged, or that might be malicious software from an unknown source.");
                    sb.AppendLine("Probably you running Win7 x64 (or more higher windows version), known solutions for starting unsigned (test signed drivers)");
                    sb.AppendLine("1) Restart your computer with no checking signature mode (F8 during boot, and select mode)");
                    sb.AppendLine("2) Win+R -> cmd -> type 'bcdedit -set TESTSIGNING ON' without qoutes, press enter and reboot. I DON'T really know will it helps to you, but on my computer, which sign the driver, it helps.");
                    sb.AppendLine("   if it not helps, try self sign the driver (it's pretty hard for person who didn't do it before, and so less information in internet, more information see in help)");
                    sb.AppendLine("3) Do not use this program, Do not use Win7 x64 ;-)");

                    ERR_STR = sb.ToString();
                }

                throw new Win32Exception((int)lastError, "There was error, while trying to start service \"" + ServiceName + "\";" + ERR_STR);
            }

            return successed;
        }

        /// <summary>
        /// Stops Specified service
        /// </summary>
        /// <param name="ServiceName">Name of service</param>
        /// <returns>True if successed, or if service was alredy stopped</returns>
        /// <exception cref="System.ComponentModel.Win32Exception"> When something wrong. Containing win32 error code</exception>
        public static bool StopService(string ServiceName)
        {
            int lastError = 0;

            bool successed = StopServiceWrapper(ServiceName, out lastError);

            if (lastError != 0 && !successed)
            {
                string ERR_STR = "";
                try
                {
                    // cast ulong to LastError can throw an exception
                    ERR_STR = Enum.GetName(typeof(ServiceLastError), lastError);
                }
                catch (System.Exception) { }
                throw new Win32Exception((int)lastError, "There were error while trying to stop service \"" + ServiceName + "\";" + ERR_STR);
            }

            return successed;
        }
   
        /// <summary>
        /// Stops Specified service
        /// </summary>
        /// <param name="ServiceName">Name of service</param>
        /// <returns>True if successed, or if service was alredy stopped</returns>
        /// <exception cref="System.ComponentModel.Win32Exception"> When something wrong. Containing win32 error code</exception>
        public static bool PauseService(string ServiceName)
        {
            int lastError = 0;

            bool successed = PauseServiceWrapper(ServiceName, out lastError);

            if (lastError != 0 && !successed)
            {
                string ERR_STR = "";
                try
                {
                    // cast ulong to LastError can throw an exception
                    ERR_STR = Enum.GetName(typeof(ServiceLastError), lastError);
                }
                catch (System.Exception) { }
                throw new Win32Exception((int)lastError, "There were error while trying to stop service \"" + ServiceName + "\";" + ERR_STR);
            }

            return successed;
        }
     
        /// <summary>
        /// Stops Specified service
        /// </summary>
        /// <param name="ServiceName">Name of service</param>
        /// <returns>True if successed, or if service was alredy stopped</returns>
        /// <exception cref="System.ComponentModel.Win32Exception"> When something wrong. Containing win32 error code</exception>
        public static bool ContinueService(string ServiceName)
        {
            int lastError = 0;

            bool successed = ContinueServiceWrapper(ServiceName, out lastError);

            if (lastError != 0 && !successed)
            {
                string ERR_STR = "";
                try
                {
                    // cast ulong to LastError can throw an exception
                    ERR_STR = Enum.GetName(typeof(ServiceLastError), lastError);
                }
                catch (System.Exception) { }
                throw new Win32Exception((int)lastError, "There were error while trying to stop service \"" + ServiceName + "\";" + ERR_STR);
            }

            return successed;
        }

        /// <summary>
        /// Installs ndisprot.sys service (Only). This isn't tool for installing drivers
        /// </summary>
        /// <returns></returns> 
        /// <exception cref="System.ComponentModel.Win32Exception"> When something wrong. Containing win32 error code</exception>
        public static bool InstallService()
        {
            int lastError = 0;

            string execPath = "";
            
            try
            {
                execPath = Application.StartupPath 
                    + (Utils.SystemUtils.Is64BitOs() ? "\\x64\\" : "\\x86\\") 
                    + ServiceMangerInternal.NdisProtServiceName + ".sys";
            }
            catch (System.ComponentModel.Win32Exception wex)
            {
	            MainWindow.LogWriteLine(
                    "Can't create path to ndisprot driver, cause can't get system architecture, using default." + Environment.NewLine + wex.Message,
                    Options.Global.LogLevel.Warning);
                
                execPath = Application.StartupPath 
                    + "\\" + Options.Global.Architecture + "\\" 
                    + ServiceMangerInternal.NdisProtServiceName + ".sys";
            }

            bool successed = InstallServiceWrapper(ServiceMangerInternal.NdisProtServiceName, out lastError, execPath);
            if (lastError != 0 && !successed)
            {
                string ERR_STR = "";
                try
                {
                    // cast ulong to LastError can throw an exception
                    ERR_STR = Enum.GetName(typeof(ServiceLastError), lastError);
                }
                catch (System.Exception) { }
                throw new Win32Exception((int)lastError, "There were error while trying to install service \"" + execPath + "\";" + ERR_STR);
            }

            return successed;
        }

        /// <summary>
        /// UnInstalles ndisprot.sys
        /// </summary>
        /// <returns></returns>
        public static bool RemoveService()
        {
            int lastError = 0;

            bool successed = RemoveServiceWrapper(ServiceMangerInternal.NdisProtServiceName, out lastError);

            if (lastError != 0 && !successed)
            {
                string ERR_STR = "";
                try
                {
                    // cast ulong to LastError can throw an exception
                    ERR_STR = Enum.GetName(typeof(ServiceLastError), lastError);
                }
                catch (System.Exception) { }
                throw new Win32Exception((int)lastError, "There were error while trying to Remove ndisprot service;" + ERR_STR);
            }

            return successed;
        }
        #endregion
    }
}
